package org.jecat.pusher;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

class ExceptionDisconnect extends Exception
{
	private static final long serialVersionUID = 3228461574052888430L;
}

public class Server
{
	public static final String version = "0.1.1" ;

	public static final int FLASH_SECURITY_PORT = 843 ;
	public static final String FLASH_SECURITY_POLICY = "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"1-60000\"/></cross-domain-policy>\0" ;
	private Selector m_aSelector;
	
	public Server(Configure aConfig) throws Exception
	{
		m_aConfig = aConfig ;
		m_nPort = aConfig.getInt("/config/port") ;
		if(m_nPort<1)
		{
			throw new Exception("missing port in setting.ini") ;
		}
		
		ArrayList<String> arrChannel = aConfig.getStringList("/config/channel") ;
		if(arrChannel==null)
		{
			throw new Exception("missing channel in setting.ini") ;
		}
		for( String sChannel:arrChannel )
		{
			createNewChannel(sChannel) ;
		}
	}
	
	public void daemon() throws IOException
	{
		log("Version:"+version) ;
		
		// 启动 843安全策略文件端口
		// -------------------------------------------
		new Thread(new Runnable(){
			@Override
			public void run()
			{
				ServerSocket aServer = null ;
				try {
					aServer = new ServerSocket(FLASH_SECURITY_PORT) ;
					log("waiting for security on port "+FLASH_SECURITY_PORT) ;
				}
				catch (IOException e)
				{
					e.printStackTrace();
					log("无法监听安全策略端口。") ;
					System.exit(0) ;
				}
					
				while(true)
				{
					Socket sock = null ;
					try {
						sock = aServer.accept();

						// 发送策略
						OutputStreamWriter output = new OutputStreamWriter(sock.getOutputStream(), "UTF-8");
						BufferedWriter writer = new BufferedWriter(output);
						writer.write(FLASH_SECURITY_POLICY);
						writer.flush();
						sock.close() ;

						log("accept flash security port");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}				
		}).start() ;

		

		m_aSelector = Selector.open();
		
		// 
		ServerSocketChannel aNIOSocketServer = ServerSocketChannel.open() ;
		aNIOSocketServer.socket().bind(new InetSocketAddress(m_nPort)) ;
		aNIOSocketServer.configureBlocking(false) ;
		aNIOSocketServer.register(m_aSelector, SelectionKey.OP_ACCEPT) ;
		
		// 
		/*ServerSocketChannel aNIOSocketServer = ServerSocketChannel.open();
		aNIOSocketServer.socket().bind(new InetSocketAddress(m_nPort));
		aNIOSocketServer.configureBlocking(false);
		aNIOSocketServer.register(m_aSelector, SelectionKey.OP_ACCEPT);*/

		log("waiting on port "+m_nPort) ;
		
		while(true){

			m_aSelector.select() ;
			
			Iterator<SelectionKey> iter = m_aSelector.selectedKeys().iterator();
			while (iter.hasNext()) {
				SelectionKey key = iter.next();
				iter.remove();

				try {
					
					if (key.isAcceptable())
					{
						accept(key) ;
					}
					else if (key.isReadable())
					{
						receive(key);
					}
					
				}
				catch (IOException e)
				{
					log(e.getMessage()) ;
				}
				
			}
		}
	}
	

	private void accept(SelectionKey key) throws IOException
	{
		SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
		channel.configureBlocking(false);
		channel.register(m_aSelector, SelectionKey.OP_READ) ;
		
		log("accept a connection, nio selection key:"+key.hashCode()) ;
	}
	private void receive(SelectionKey key)
	{		
		Session session = Session.getSession(key) ;
		
		Request req = session.request() ;
		
		// 接收
		try {
			req.receive((SocketChannel)key.channel()) ;
			
			while( req.hasData() )
			{
				req.buildRequest() ;

				// 新请求完成
				if( !req.requestClose() && req.avalid() )
				{
					request(session) ;
				}
			}
			
		} catch (ExceptionDisconnect e) {
			disconnect(session) ;
			return ;
		}
		
	}

		
	public void request(Session session)
	{
		Request req = session.request() ;
		SelectionKey key = session.nioSelectionKey() ;
		
		String sReqChannel = req.get("channel","") ;
		Channel aChannel = m_mapChannels.get(sReqChannel) ;

		String sAction = req.get("action","connect") ;
		
		////////////////////////////////////////////////
		// 客户端连接请求
		if(sAction.equals("connect"))
		{
			if(aChannel==null)
			{
				send("channel name invalid.",(SocketChannel) key.channel()) ;
				session.destroy() ;
				return ;
			}
			String oauthReturn = "0";
			try {
				String url = "/oauth.php?uid=" + req.get("id") + "&password=" + req.get("password");
				Charset charset = Charset.forName("UTF-8");
				InetSocketAddress socketAddress = new InetSocketAddress("http://www.wownei.com", 80);
				SocketChannel channel = SocketChannel.open(socketAddress);
				channel.write(charset.encode("GET " + url + "\r\n"));
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				while (channel.read(buffer) != -1) {
					buffer.flip();
					oauthReturn = charset.decode(buffer).toString();
					buffer.clear();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			if(oauthReturn.equals("0")){
				disconnect(session);
			}else{
				session.request().requestClose(true) ;
				aChannel.add( session ) ;
			}
		}

		////////////////////////////////////////////////
		// push数据请求
		else if(sAction.equals("policy"))
		{
			send(FLASH_SECURITY_POLICY,(SocketChannel) key.channel()) ;
			session.request().drop() ;
		}

		////////////////////////////////////////////////
		// push数据请求
		else if(sAction.equals("push"))
		{
			if(aChannel==null)
			{
				send("channel name invalid.",(SocketChannel) key.channel()) ;
				session.request().drop() ;
				return ;
			}
			
			// 检查密码
			if( m_aConfig.getString("/config/pushpassword").equals(req.get("password","")) )
			{
				Channel aToChannel ;
				String sChannelName = req.get("channel") ;
				if( sChannelName==null || (aToChannel=m_mapChannels.get(sChannelName))==null )
				{
					send("missing parameter channel or parameter invalid.",(SocketChannel) key.channel()) ;
				}
				
				else
				{
					aToChannel.pushData(req, (SocketChannel) key.channel()) ;
				}
			}
			
			else
			{
				send("Permission denied",(SocketChannel) key.channel()) ;
			}
			
			session.request().drop() ;
		}
	}

	

	public Channel createNewChannel(String sName)
	{
		Channel aChannel = new Channel(sName) ;
		
		m_mapChannels.put(sName,aChannel) ;
		
		return aChannel ;
	}
	
	public Configure config()
	{
		return m_aConfig ;
	}

	public void disconnect(Session session)
	{
		Channel aChannel = Channel.findChannel(m_mapChannels, session) ;
		if(aChannel!=null)
		{
			aChannel.remove(session) ;
		}
		
		session.destroy() ;
	}
	
	public static void send(String sData,SocketChannel channel) 
	{
		byte[] bytes = sData.getBytes() ;

		ByteBuffer writeBuff = ByteBuffer.allocate(bytes.length) ;
		writeBuff.put(bytes) ;
		
		writeBuff.flip() ;
		try {
			channel.write(writeBuff) ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		writeBuff.clear() ;
	}

	public static void sendPackage( String sStreamData, SocketChannel channel )
	{
		try
		{
			Boolean bCompress = false ;
			
			byte[] arrDatas = sStreamData.getBytes("utf-8") ;
			ByteBuffer writeBuff = ByteBuffer.allocate( 1+2+1+arrDatas.length+1 ) ;

			writeBuff.put((byte)0x04) ;
			writeBuff.put((byte)((arrDatas.length&0xFF00)>>8)) ;
			writeBuff.put((byte)(arrDatas.length&0x00FF)) ;
			writeBuff.put((byte)(bCompress?1:0)) ;
			writeBuff.put(arrDatas) ;
			writeBuff.put((byte)0x03) ;
			
			writeBuff.flip() ;
			channel.write(writeBuff) ;
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace() ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void log(String sMessage)
	{
		SimpleDateFormat formatter = new SimpleDateFormat("yyyy.M.d H:m:s");
		System.out.println("["+formatter.format(System.currentTimeMillis())+"] "+sMessage) ;
	}
		

	private Configure m_aConfig ;
	private Hashtable<String,Channel> m_mapChannels = new Hashtable<String,Channel>() ;
	
	private int m_nPort ;


}
